/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Buffer test utilities.
 *//*--------------------------------------------------------------------*/

#include "glsBufferTestUtil.hpp"
#include "tcuRandomValueIterator.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTestLog.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "deMemory.h"
#include "deStringUtil.hpp"
#include "deArrayUtil.hpp"

#include <algorithm>

#include "glwEnums.hpp"
#include "glwFunctions.hpp"

namespace deqp
{
namespace gls
{
namespace BufferTestUtil
{

enum
{
	VERIFY_QUAD_SIZE					= 8,		//!< Quad size in VertexArrayVerifier
	MAX_LINES_PER_INDEX_ARRAY_DRAW		= 128,		//!< Maximum number of lines per one draw in IndexArrayVerifier
	INDEX_ARRAY_DRAW_VIEWPORT_WIDTH		= 128,
	INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT	= 128
};

using tcu::TestLog;
using std::vector;
using std::string;
using std::set;

// Helper functions.

void fillWithRandomBytes (deUint8* ptr, int numBytes, deUint32 seed)
{
	std::copy(tcu::RandomValueIterator<deUint8>::begin(seed, numBytes), tcu::RandomValueIterator<deUint8>::end(), ptr);
}

bool compareByteArrays (tcu::TestLog& log, const deUint8* resPtr, const deUint8* refPtr, int numBytes)
{
	bool			isOk			= true;
	const int		maxSpanLen		= 8;
	const int		maxDiffSpans	= 4;
	int				numDiffSpans	= 0;
	int				diffSpanStart	= -1;
	int				ndx				= 0;

	log << TestLog::Section("Verify", "Verification result");

	for (;ndx < numBytes; ndx++)
	{
		if (resPtr[ndx] != refPtr[ndx])
		{
			if (diffSpanStart < 0)
				diffSpanStart = ndx;

			isOk = false;
		}
		else if (diffSpanStart >= 0)
		{
			if (numDiffSpans < maxDiffSpans)
			{
				int len			= ndx-diffSpanStart;
				int	printLen	= de::min(len, maxSpanLen);

				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
					<< TestLog::EndMessage;
			}
			else
				log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;

			numDiffSpans	+= 1;
			diffSpanStart	 = -1;
		}
	}

	if (diffSpanStart >= 0)
	{
		if (numDiffSpans < maxDiffSpans)
		{
				int len			= ndx-diffSpanStart;
				int	printLen	= de::min(len, maxSpanLen);

				log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
										<< "  expected "	<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(refPtr+diffSpanStart+printLen)) << "\n"
										<< "  got "			<< tcu::formatArray(tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart), tcu::Format::HexIterator<deUint8>(resPtr+diffSpanStart+printLen))
					<< TestLog::EndMessage;
		}
		else
			log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
	}

	log << TestLog::Message << (isOk ? "Verification passed." : "Verification FAILED!") << TestLog::EndMessage;
	log << TestLog::EndSection;

	return isOk;
}

const char* getBufferTargetName (deUint32 target)
{
	switch (target)
	{
		case GL_ARRAY_BUFFER:				return "array";
		case GL_COPY_READ_BUFFER:			return "copy_read";
		case GL_COPY_WRITE_BUFFER:			return "copy_write";
		case GL_ELEMENT_ARRAY_BUFFER:		return "element_array";
		case GL_PIXEL_PACK_BUFFER:			return "pixel_pack";
		case GL_PIXEL_UNPACK_BUFFER:		return "pixel_unpack";
		case GL_TEXTURE_BUFFER:				return "texture";
		case GL_TRANSFORM_FEEDBACK_BUFFER:	return "transform_feedback";
		case GL_UNIFORM_BUFFER:				return "uniform";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

const char* getUsageHintName (deUint32 hint)
{
	switch (hint)
	{
		case GL_STREAM_DRAW:	return "stream_draw";
		case GL_STREAM_READ:	return "stream_read";
		case GL_STREAM_COPY:	return "stream_copy";
		case GL_STATIC_DRAW:	return "static_draw";
		case GL_STATIC_READ:	return "static_read";
		case GL_STATIC_COPY:	return "static_copy";
		case GL_DYNAMIC_DRAW:	return "dynamic_draw";
		case GL_DYNAMIC_READ:	return "dynamic_read";
		case GL_DYNAMIC_COPY:	return "dynamic_copy";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

// BufferCase

BufferCase::BufferCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description)
	: TestCase			(testCtx, name, description)
	, CallLogWrapper	(renderCtx.getFunctions(), testCtx.getLog())
	, m_renderCtx		(renderCtx)
{
}

BufferCase::~BufferCase (void)
{
	enableLogging(false);
	BufferCase::deinit();
}

void BufferCase::init (void)
{
	enableLogging(true);
}

void BufferCase::deinit (void)
{
	for (set<deUint32>::const_iterator bufIter = m_allocatedBuffers.begin(); bufIter != m_allocatedBuffers.end(); bufIter++)
		glDeleteBuffers(1, &(*bufIter));
}

deUint32 BufferCase::genBuffer (void)
{
	deUint32 buf = 0;
	glGenBuffers(1, &buf);
	if (buf != 0)
	{
		try
		{
			m_allocatedBuffers.insert(buf);
		}
		catch (const std::exception&)
		{
			glDeleteBuffers(1, &buf);
			throw;
		}
	}
	return buf;
}

void BufferCase::deleteBuffer (deUint32 buffer)
{
	glDeleteBuffers(1, &buffer);
	m_allocatedBuffers.erase(buffer);
}

void BufferCase::checkError (void)
{
	glw::GLenum err = glGetError();
	if (err != GL_NO_ERROR)
		throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
}

// ReferenceBuffer

void ReferenceBuffer::setSize (int numBytes)
{
	m_data.resize(numBytes);
}

void ReferenceBuffer::setData (int numBytes, const deUint8* bytes)
{
	m_data.resize(numBytes);
	std::copy(bytes, bytes+numBytes, m_data.begin());
}

void ReferenceBuffer::setSubData (int offset, int numBytes, const deUint8* bytes)
{
	DE_ASSERT(de::inBounds(offset, 0, (int)m_data.size()) && de::inRange(offset+numBytes, offset, (int)m_data.size()));
	std::copy(bytes, bytes+numBytes, m_data.begin()+offset);
}

// BufferWriterBase

BufferWriterBase::BufferWriterBase (glu::RenderContext& renderCtx, tcu::TestLog& log)
	: CallLogWrapper	(renderCtx.getFunctions(), log)
	, m_renderCtx		(renderCtx)
{
	enableLogging(true);
}

void BufferWriterBase::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint)
{
	DE_UNREF(targetHint);
	write(buffer, offset, numBytes, bytes);
}

// BufferWriter

BufferWriter::BufferWriter (glu::RenderContext& renderCtx, tcu::TestLog& log, WriteType writeType)
	: m_writer(DE_NULL)
{
	switch (writeType)
	{
		case WRITE_BUFFER_SUB_DATA:		m_writer = new BufferSubDataWriter	(renderCtx, log);	break;
		case WRITE_BUFFER_WRITE_MAP:	m_writer = new BufferWriteMapWriter	(renderCtx, log);	break;
		default:
			TCU_FAIL("Unsupported writer");
	}
}

BufferWriter::~BufferWriter (void)
{
	delete m_writer;
}

void BufferWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
{
	DE_ASSERT(numBytes >= getMinSize());
	DE_ASSERT(offset%getAlignment() == 0);
	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
	return m_writer->write(buffer, offset, numBytes, bytes);
}

void BufferWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 targetHint)
{
	DE_ASSERT(numBytes >= getMinSize());
	DE_ASSERT(offset%getAlignment() == 0);
	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
	return m_writer->write(buffer, offset, numBytes, bytes, targetHint);
}

// BufferSubDataWriter

void BufferSubDataWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
{
	write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
}

void BufferSubDataWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target)
{
	glBindBuffer(target, buffer);
	glBufferSubData(target, offset, numBytes, bytes);
	glBindBuffer(target, 0);
	GLU_CHECK();
}

// BufferWriteMapWriter

void BufferWriteMapWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes)
{
	write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
}

void BufferWriteMapWriter::write (deUint32 buffer, int offset, int numBytes, const deUint8* bytes, deUint32 target)
{
	glBindBuffer(target, buffer);

	void* ptr = glMapBufferRange(target, offset, numBytes, GL_MAP_WRITE_BIT);
	GLU_CHECK_MSG("glMapBufferRange");

	deMemcpy(ptr, bytes, numBytes);

	glUnmapBuffer(target);
	glBindBuffer(target, 0);
	GLU_CHECK();
}

// BufferVerifierBase

BufferVerifierBase::BufferVerifierBase (glu::RenderContext& renderCtx, tcu::TestLog& log)
	: CallLogWrapper	(renderCtx.getFunctions(), log)
	, m_renderCtx		(renderCtx)
	, m_log				(log)
{
	enableLogging(true);
}

bool BufferVerifierBase::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint)
{
	DE_UNREF(targetHint);
	return verify(buffer, reference, offset, numBytes);
}

// BufferVerifier

BufferVerifier::BufferVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log, VerifyType verifyType)
	: m_verifier(DE_NULL)
{
	switch (verifyType)
	{
		case VERIFY_AS_VERTEX_ARRAY:	m_verifier = new VertexArrayVerifier(renderCtx, log);	break;
		case VERIFY_AS_INDEX_ARRAY:		m_verifier = new IndexArrayVerifier	(renderCtx, log);	break;
		case VERIFY_BUFFER_READ_MAP:	m_verifier = new BufferMapVerifier	(renderCtx, log);	break;
		default:
			TCU_FAIL("Unsupported verifier");
	}
}

BufferVerifier::~BufferVerifier (void)
{
	delete m_verifier;
}

bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes)
{
	DE_ASSERT(numBytes >= getMinSize());
	DE_ASSERT(offset%getAlignment() == 0);
	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
	return m_verifier->verify(buffer, reference, offset, numBytes);
}

bool BufferVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 targetHint)
{
	DE_ASSERT(numBytes >= getMinSize());
	DE_ASSERT(offset%getAlignment() == 0);
	DE_ASSERT((offset+numBytes)%getAlignment() == 0);
	return m_verifier->verify(buffer, reference, offset, numBytes, targetHint);
}

// BufferMapVerifier

bool BufferMapVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes)
{
	return verify(buffer, reference, offset, numBytes, GL_ARRAY_BUFFER);
}

bool BufferMapVerifier::verify (deUint32 buffer, const deUint8* reference, int offset, int numBytes, deUint32 target)
{
	const deUint8*	mapPtr		= DE_NULL;
	bool			isOk		= false;

	glBindBuffer(target, buffer);
	mapPtr = (const deUint8*)glMapBufferRange(target, offset, numBytes, GL_MAP_READ_BIT);
	GLU_CHECK_MSG("glMapBufferRange");
	TCU_CHECK(mapPtr);

	isOk = compareByteArrays(m_log, mapPtr, reference+offset, numBytes);

	glUnmapBuffer(target);
	GLU_CHECK_MSG("glUnmapBuffer");

	glBindBuffer(target, 0);

	return isOk;
}

// VertexArrayVerifier

VertexArrayVerifier::VertexArrayVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log)
	: BufferVerifierBase	(renderCtx, log)
	, m_program				(DE_NULL)
	, m_posLoc				(0)
	, m_byteVecLoc			(0)
	, m_vao					(0)
{
	const glu::ContextType	ctxType		= renderCtx.getType();
	const glu::GLSLVersion	glslVersion	= glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;

	DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));

	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(
		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
		"in highp vec2 a_position;\n"
		"in mediump vec3 a_byteVec;\n"
		"out mediump vec3 v_byteVec;\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_byteVec = a_byteVec;\n"
		"}\n",

		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
		"in mediump vec3 v_byteVec;\n"
		"layout(location = 0) out mediump vec4 o_color;\n"
		"void main (void)\n"
		"{\n"
		"	o_color = vec4(v_byteVec, 1.0);\n"
		"}\n"));

	if (!m_program->isOk())
	{
		m_log << *m_program;
		delete m_program;
		TCU_FAIL("Compile failed");
	}

	const glw::Functions& gl = m_renderCtx.getFunctions();
	m_posLoc		= gl.getAttribLocation(m_program->getProgram(), "a_position");
	m_byteVecLoc	= gl.getAttribLocation(m_program->getProgram(), "a_byteVec");

	gl.genVertexArrays(1, &m_vao);
	gl.genBuffers(1, &m_positionBuf);
	gl.genBuffers(1, &m_indexBuf);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
}

VertexArrayVerifier::~VertexArrayVerifier (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_vao)			gl.deleteVertexArrays(1, &m_vao);
	if (m_positionBuf)	gl.deleteBuffers(1, &m_positionBuf);
	if (m_indexBuf)		gl.deleteBuffers(1, &m_indexBuf);

	delete m_program;
}

static void computePositions (vector<tcu::Vec2>& positions, int gridSizeX, int gridSizeY)
{
	positions.resize(gridSizeX*gridSizeY*4);

	for (int y = 0; y < gridSizeY; y++)
	for (int x = 0; x < gridSizeX; x++)
	{
		float	sx0			= (float)(x+0) / (float)gridSizeX;
		float	sy0			= (float)(y+0) / (float)gridSizeY;
		float	sx1			= (float)(x+1) / (float)gridSizeX;
		float	sy1			= (float)(y+1) / (float)gridSizeY;
		float	fx0			= 2.0f * sx0 - 1.0f;
		float	fy0			= 2.0f * sy0 - 1.0f;
		float	fx1			= 2.0f * sx1 - 1.0f;
		float	fy1			= 2.0f * sy1 - 1.0f;
		int		baseNdx		= (y * gridSizeX + x)*4;

		positions[baseNdx+0] = tcu::Vec2(fx0, fy0);
		positions[baseNdx+1] = tcu::Vec2(fx0, fy1);
		positions[baseNdx+2] = tcu::Vec2(fx1, fy0);
		positions[baseNdx+3] = tcu::Vec2(fx1, fy1);
	}
}

static void computeIndices (vector<deUint16>& indices, int gridSizeX, int gridSizeY)
{
	indices.resize(3 * 2 * gridSizeX * gridSizeY);

	for (int quadNdx = 0; quadNdx < gridSizeX*gridSizeY; quadNdx++)
	{
		int v00 = quadNdx*4 + 0;
		int v01 = quadNdx*4 + 1;
		int v10 = quadNdx*4 + 2;
		int v11 = quadNdx*4 + 3;

		DE_ASSERT(v11 < (1<<16));

		indices[quadNdx*6 + 0] = (deUint16)v10;
		indices[quadNdx*6 + 1] = (deUint16)v00;
		indices[quadNdx*6 + 2] = (deUint16)v01;

		indices[quadNdx*6 + 3] = (deUint16)v10;
		indices[quadNdx*6 + 4] = (deUint16)v01;
		indices[quadNdx*6 + 5] = (deUint16)v11;
	}
}

static inline tcu::Vec4 fetchVtxColor (const deUint8* ptr, int vtxNdx)
{
	return tcu::RGBA(*(ptr + vtxNdx*3 + 0),
					 *(ptr + vtxNdx*3 + 1),
					 *(ptr + vtxNdx*3 + 2),
					 255).toVec();
}

static void renderQuadGridReference (tcu::Surface& dst, int numQuads, int rowLength, const deUint8* inPtr)
{
	using tcu::Vec4;

	dst.setSize(rowLength*VERIFY_QUAD_SIZE, (numQuads/rowLength + (numQuads%rowLength != 0 ? 1 : 0))*VERIFY_QUAD_SIZE);

	tcu::PixelBufferAccess dstAccess = dst.getAccess();
	tcu::clear(dstAccess, tcu::IVec4(0, 0, 0, 0xff));

	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
	{
		int		x0		= (quadNdx%rowLength)*VERIFY_QUAD_SIZE;
		int		y0		= (quadNdx/rowLength)*VERIFY_QUAD_SIZE;
		Vec4	v00		= fetchVtxColor(inPtr, quadNdx*4 + 0);
		Vec4	v10		= fetchVtxColor(inPtr, quadNdx*4 + 1);
		Vec4	v01		= fetchVtxColor(inPtr, quadNdx*4 + 2);
		Vec4	v11		= fetchVtxColor(inPtr, quadNdx*4 + 3);

		for (int y = 0; y < VERIFY_QUAD_SIZE; y++)
		for (int x = 0; x < VERIFY_QUAD_SIZE; x++)
		{
			float		fx		= ((float)x+0.5f) / (float)VERIFY_QUAD_SIZE;
			float		fy		= ((float)y+0.5f) / (float)VERIFY_QUAD_SIZE;

			bool		tri		= fx + fy <= 1.0f;
			float		tx		= tri ? fx : (1.0f-fx);
			float		ty		= tri ? fy : (1.0f-fy);
			const Vec4&	t0		= tri ? v00 : v11;
			const Vec4&	t1		= tri ? v01 : v10;
			const Vec4&	t2		= tri ? v10 : v01;
			Vec4		color	= t0 + (t1-t0)*tx + (t2-t0)*ty;

			dstAccess.setPixel(color, x0+x, y0+y);
		}
	}
}

bool VertexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
{
	const tcu::RenderTarget&	renderTarget		= m_renderCtx.getRenderTarget();
	const int					numBytesInVtx		= 3;
	const int					numBytesInQuad		= numBytesInVtx*4;
	int							maxQuadsX			= de::min(128, renderTarget.getWidth()	/ VERIFY_QUAD_SIZE);
	int							maxQuadsY			= de::min(128, renderTarget.getHeight()	/ VERIFY_QUAD_SIZE);
	int							maxQuadsPerBatch	= maxQuadsX*maxQuadsY;
	int							numVerified			= 0;
	deUint32					program				= m_program->getProgram();
	tcu::RGBA					threshold			= renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(3,3,3,3);
	bool						isOk				= true;

	vector<tcu::Vec2>			positions;
	vector<deUint16>			indices;

	tcu::Surface				rendered;
	tcu::Surface				reference;

	DE_ASSERT(numBytes >= numBytesInQuad); // Can't render full quad with smaller buffers.

	computePositions(positions, maxQuadsX, maxQuadsY);
	computeIndices(indices, maxQuadsX, maxQuadsY);

	// Reset buffer bindings.
	glBindBuffer				(GL_PIXEL_PACK_BUFFER, 0);

	// Setup rendering state.
	glViewport					(0, 0, maxQuadsX*VERIFY_QUAD_SIZE, maxQuadsY*VERIFY_QUAD_SIZE);
	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
	glUseProgram				(program);
	glBindVertexArray			(m_vao);

	// Upload positions
	glBindBuffer				(GL_ARRAY_BUFFER, m_positionBuf);
	glBufferData				(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size()*sizeof(positions[0])), &positions[0], GL_STATIC_DRAW);
	glEnableVertexAttribArray	(m_posLoc);
	glVertexAttribPointer		(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

	// Upload indices
	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER, m_indexBuf);
	glBufferData				(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size()*sizeof(indices[0])), &indices[0], GL_STATIC_DRAW);

	glEnableVertexAttribArray	(m_byteVecLoc);
	glBindBuffer				(GL_ARRAY_BUFFER, buffer);

	while (numVerified < numBytes)
	{
		int		numRemaining		= numBytes-numVerified;
		bool	isLeftoverBatch		= numRemaining < numBytesInQuad;
		int		numBytesToVerify	= isLeftoverBatch ? numBytesInQuad				: de::min(maxQuadsPerBatch*numBytesInQuad, numRemaining - numRemaining%numBytesInQuad);
		int		curOffset			= isLeftoverBatch ? (numBytes-numBytesInQuad)	: numVerified;
		int		numQuads			= numBytesToVerify/numBytesInQuad;
		int		numCols				= de::min(maxQuadsX, numQuads);
		int		numRows				= numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0);
		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);

		DE_ASSERT(numBytesToVerify > 0 && numBytesToVerify%numBytesInQuad == 0);
		DE_ASSERT(de::inBounds(curOffset, 0, numBytes));
		DE_ASSERT(de::inRange(curOffset+numBytesToVerify, curOffset, numBytes));

		// Render batch.
		glClear					(GL_COLOR_BUFFER_BIT);
		glVertexAttribPointer	(m_byteVecLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const glw::GLvoid*)(deUintptr)(offset + curOffset));
		glDrawElements			(GL_TRIANGLES, numQuads*6, GL_UNSIGNED_SHORT, DE_NULL);

		renderQuadGridReference(reference,  numQuads, numCols, refPtr + offset + curOffset);

		rendered.setSize(numCols*VERIFY_QUAD_SIZE, numRows*VERIFY_QUAD_SIZE);
		glu::readPixels(m_renderCtx, 0, 0, rendered.getAccess());

		if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT))
		{
			isOk = false;
			break;
		}

		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
	}

	glBindVertexArray(0);

	return isOk;
}

// IndexArrayVerifier

IndexArrayVerifier::IndexArrayVerifier (glu::RenderContext& renderCtx, tcu::TestLog& log)
	: BufferVerifierBase	(renderCtx, log)
	, m_program				(DE_NULL)
	, m_posLoc				(0)
	, m_colorLoc			(0)
{

	const glu::ContextType	ctxType		= renderCtx.getType();
	const glu::GLSLVersion	glslVersion	= glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;

	DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));

	m_program = new glu::ShaderProgram(m_renderCtx, glu::makeVtxFragSources(
		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
		"in highp vec2 a_position;\n"
		"in mediump vec3 a_color;\n"
		"out mediump vec3 v_color;\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_color = a_color;\n"
		"}\n",

		string(glu::getGLSLVersionDeclaration(glslVersion)) + "\n"
		"in mediump vec3 v_color;\n"
		"layout(location = 0) out mediump vec4 o_color;\n"
		"void main (void)\n"
		"{\n"
		"	o_color = vec4(v_color, 1.0);\n"
		"}\n"));

	if (!m_program->isOk())
	{
		m_log << *m_program;
		delete m_program;
		TCU_FAIL("Compile failed");
	}

	const glw::Functions& gl = m_renderCtx.getFunctions();
	m_posLoc	= gl.getAttribLocation(m_program->getProgram(), "a_position");
	m_colorLoc	= gl.getAttribLocation(m_program->getProgram(), "a_color");

	gl.genVertexArrays(1, &m_vao);
	gl.genBuffers(1, &m_positionBuf);
	gl.genBuffers(1, &m_colorBuf);
	GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
}

IndexArrayVerifier::~IndexArrayVerifier (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_vao)			gl.deleteVertexArrays(1, &m_vao);
	if (m_positionBuf)	gl.deleteBuffers(1, &m_positionBuf);
	if (m_colorBuf)		gl.deleteBuffers(1, &m_colorBuf);

	delete m_program;
}

static void computeIndexVerifierPositions (std::vector<tcu::Vec2>& dst)
{
	const int	numPosX		= 16;
	const int	numPosY		= 16;

	dst.resize(numPosX*numPosY);

	for (int y = 0; y < numPosY; y++)
	{
		for (int x = 0; x < numPosX; x++)
		{
			float	xf	= float(x) / float(numPosX-1);
			float	yf	= float(y) / float(numPosY-1);

			dst[y*numPosX + x] = tcu::Vec2(2.0f*xf - 1.0f, 2.0f*yf - 1.0f);
		}
	}
}

static void computeIndexVerifierColors (std::vector<tcu::Vec3>& dst)
{
	const int	numColors	= 256;
	const float	minVal		= 0.1f;
	const float maxVal		= 0.5f;
	de::Random	rnd			(0xabc231);

	dst.resize(numColors);

	for (std::vector<tcu::Vec3>::iterator i = dst.begin(); i != dst.end(); ++i)
	{
		i->x()	= rnd.getFloat(minVal, maxVal);
		i->y()	= rnd.getFloat(minVal, maxVal);
		i->z()	= rnd.getFloat(minVal, maxVal);
	}
}

template<typename T>
static void execVertexFetch (T* dst, const T* src, const deUint8* indices, int numIndices)
{
	for (int i = 0; i < numIndices; ++i)
		dst[i] = src[indices[i]];
}

bool IndexArrayVerifier::verify (deUint32 buffer, const deUint8* refPtr, int offset, int numBytes)
{
	const tcu::RenderTarget&	renderTarget		= m_renderCtx.getRenderTarget();
	const int					viewportW			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_WIDTH, renderTarget.getWidth());
	const int					viewportH			= de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT, renderTarget.getHeight());
	const int					minBytesPerBatch	= 2;
	const tcu::RGBA				threshold			(0,0,0,0);

	std::vector<tcu::Vec2>		positions;
	std::vector<tcu::Vec3>		colors;

	std::vector<tcu::Vec2>		fetchedPos			(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);
	std::vector<tcu::Vec3>		fetchedColor		(MAX_LINES_PER_INDEX_ARRAY_DRAW+1);

	tcu::Surface				indexBufferImg		(viewportW, viewportH);
	tcu::Surface				referenceImg		(viewportW, viewportH);

	int							numVerified			= 0;
	bool						isOk				= true;

	DE_STATIC_ASSERT(sizeof(tcu::Vec2) == sizeof(float)*2);
	DE_STATIC_ASSERT(sizeof(tcu::Vec3) == sizeof(float)*3);

	computeIndexVerifierPositions(positions);
	computeIndexVerifierColors(colors);

	// Reset buffer bindings.
	glBindVertexArray			(m_vao);
	glBindBuffer				(GL_PIXEL_PACK_BUFFER,		0);
	glBindBuffer				(GL_ELEMENT_ARRAY_BUFFER,	buffer);

	// Setup rendering state.
	glViewport					(0, 0, viewportW, viewportH);
	glClearColor				(0.0f, 0.0f, 0.0f, 1.0f);
	glUseProgram				(m_program->getProgram());
	glEnableVertexAttribArray	(m_posLoc);
	glEnableVertexAttribArray	(m_colorLoc);
	glEnable					(GL_BLEND);
	glBlendFunc					(GL_ONE, GL_ONE);
	glBlendEquation				(GL_FUNC_ADD);

	while (numVerified < numBytes)
	{
		int		numRemaining		= numBytes-numVerified;
		bool	isLeftoverBatch		= numRemaining < minBytesPerBatch;
		int		numBytesToVerify	= isLeftoverBatch ? minBytesPerBatch			: de::min(MAX_LINES_PER_INDEX_ARRAY_DRAW+1, numRemaining);
		int		curOffset			= isLeftoverBatch ? (numBytes-minBytesPerBatch)	: numVerified;
		string	imageSetDesc		= string("Bytes ") + de::toString(offset+curOffset) + " to " + de::toString(offset+curOffset+numBytesToVerify-1);

		// Step 1: Render using index buffer.
		glClear					(GL_COLOR_BUFFER_BIT);

		glBindBuffer			(GL_ARRAY_BUFFER, m_positionBuf);
		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size()*sizeof(positions[0])), &positions[0], GL_STREAM_DRAW);
		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

		glBindBuffer			(GL_ARRAY_BUFFER, m_colorBuf);
		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(colors.size()*sizeof(colors[0])), &colors[0], GL_STREAM_DRAW);
		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);

		glDrawElements			(GL_LINE_STRIP, numBytesToVerify, GL_UNSIGNED_BYTE, (void*)(deUintptr)(offset+curOffset));
		glu::readPixels			(m_renderCtx, 0, 0, indexBufferImg.getAccess());

		// Step 2: Do manual fetch and render without index buffer.
		execVertexFetch(&fetchedPos[0], &positions[0], refPtr+offset+curOffset, numBytesToVerify);
		execVertexFetch(&fetchedColor[0], &colors[0], refPtr+offset+curOffset, numBytesToVerify);

		glClear					(GL_COLOR_BUFFER_BIT);

		glBindBuffer			(GL_ARRAY_BUFFER, m_positionBuf);
		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedPos.size()*sizeof(fetchedPos[0])), &fetchedPos[0], GL_STREAM_DRAW);
		glVertexAttribPointer	(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

		glBindBuffer			(GL_ARRAY_BUFFER, m_colorBuf);
		glBufferData			(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedColor.size()*sizeof(fetchedColor[0])), &fetchedColor[0], GL_STREAM_DRAW);
		glVertexAttribPointer	(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);

		glDrawArrays			(GL_LINE_STRIP, 0, numBytesToVerify);
		glu::readPixels			(m_renderCtx, 0, 0, referenceImg.getAccess());

		if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), referenceImg, indexBufferImg, threshold, tcu::COMPARE_LOG_RESULT))
		{
			isOk = false;
			break;
		}

		numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
	}

	glBindVertexArray(0);

	return isOk;
}

const char* getWriteTypeDescription (WriteType write)
{
	static const char* s_desc[] =
	{
		"glBufferSubData()",
		"glMapBufferRange()",
		"transform feedback",
		"glReadPixels() into PBO binding"
	};
	return de::getSizedArrayElement<WRITE_LAST>(s_desc, write);
}

const char* getVerifyTypeDescription (VerifyType verify)
{
	static const char* s_desc[] =
	{
		"rendering as vertex data",
		"rendering as index data",
		"reading in shader as uniform buffer data",
		"using as PBO and uploading to texture",
		"reading back using glMapBufferRange()"
	};
	return de::getSizedArrayElement<VERIFY_LAST>(s_desc, verify);
}

} // BufferTestUtil
} // gls
} // deqp
